/*
 * sys.c - Syscalls implementation
 */

#include <devices.h>
#include <sys.h>
#include <error.h>
#include <utils.h>
#include <io.h>
#include <sched.h>
#include <mm.h>
#include <stats.h>
#include <sched.h>

int sys_write(int fd,char *buffer, int size)
{
	int error = 0;
	int oper = OP_WRITE;
	int result = 0;
	char sys_buf[BUFFER_SIZE];

	/*Checking parameters*/
	error = check_fd(fd, oper);
	if (error!=0)
	{
		return error; /*operation not permitted or file descriptor in bad state*/
	}

	if (buffer==NULL) return (-EFAULT);

	if (size<0) return (-EINVAL);
	if (size==0) return 0;

	/*Copying from user and writing to the console*/
	int i = 0;
	for(i=0;size > 0;i++)
	{
		if(size < BUFFER_SIZE)
		{
			error=copy_from_user(buffer+BUFFER_SIZE*i,sys_buf,size);
			if(error < 0) return (-EFAULT);
			error=sys_write_console(sys_buf,size);
		}
		else
		{
			error=copy_from_user(buffer+BUFFER_SIZE*i,sys_buf,BUFFER_SIZE);
			if(error < 0) return (-EFAULT);
			error=sys_write_console(sys_buf,BUFFER_SIZE);
		}
		size -= BUFFER_SIZE;
		result += error;
	}

	return result;
}


int sys_ni_call()
{
	return (-ENOSYS); /* Function not implemented */
}

int sys_getpid()
{
	return current()->pid;
}

int sys_fork()
{
	/*** Look for a free entry in the process table. If there is no space return error */

	int index = find_free_task(); //an index of a free task
	if (index<0) return (-EAGAIN); //ERROR: there are no free entries in task table
	

	/*** Search physical pages in which to map logical pages for data+stack of the child process */
	unsigned int allocated_frames[NUM_PAG_DATA];
	int i, j;
	for (i=0;i<NUM_PAG_DATA;i++)
	{
		allocated_frames[i]=alloc_frame();
		if (allocated_frames[i]<0) 
		{
			for (j=0;j<i+1;j++) free_frame (allocated_frames[j]);
			return (-ENOMEM); //ERROR: frame couldn't be allocated
		}
	}	

	/*** Copy the parent's task_union to the child */
	struct task_struct* cur = current();
	copy_data(cur,&task[index].t.task,KERNEL_STACK_SIZE*4);
	
	/*** Copy the user data+stack from the parent process to the child process */
	
	for (i=0; i<NUM_PAG_DATA;i++)
	{
		set_ss_pag(PAG_LOG_INIT_DATA_P0+NUM_PAG_DATA+i,allocated_frames[i]);			
		copy_data((void*)((PAG_LOG_INIT_DATA+i)*PAGE_SIZE),(void*)((PAG_LOG_INIT_DATA+NUM_PAG_DATA+i)*PAGE_SIZE),PAGE_SIZE);
		del_ss_pag(PAG_LOG_INIT_DATA+NUM_PAG_DATA+i);
	}

	/*** Flush TLB after making changes in the page table */
	set_cr3();

	/*** Store information about physical pages in the child's task_struct */	
	for (i=0; i<NUM_PAG_DATA;i++)
	{
		task[index].t.task.frames[i] = allocated_frames[i];
	}
	
	/*** Initialize the fields of the task_struct that are not common to the child: reset stats and inherit quantum */
	task[index].t.task.state = READY;
	task[index].t.task.quantum = cur->quantum;
	task[index].t.task.total_trans = 0;
	task[index].t.task.total_tics = 0;

	/*** Assign a new PID to the process */
	pid++;
	task[index].t.task.pid = pid;

	/*** Insert the new process into the ready list:runqueue, at the tail of the list */
	list_add_tail(&task[index].t.task.list,&runqueue);
	
	/*** Set %eax in the stack to 0 */
	task[index].t.stack[KERNEL_STACK_SIZE-10]=0;
	
	/*** Return the pid of the child process */
	return pid;
	
}

int sys_nice(int quantum)
{
	if(quantum <= 0) return -EINVAL; /* ERROR */        
	int old_quantum = current()->quantum;
        current()->quantum = quantum;
        return old_quantum;
}


void sys_exit()
{	
	struct task_struct* task = current();
	struct list_head* aux;
	int i;
		
	if (task->pid!=0)
	{
		/* searching for semaphores that this process own, if is any terminates it */	
		for (i=0; i<NR_SEM;i++) sys_sem_destroy(i);
		/* taking out from the runqueue **/
		list_del(list_first(&runqueue));
		/* releasing data frames of the process*/
		for (i=0;i<NUM_PAG_DATA;i++)
			free_frame(task->frames[i]);
	
		/*cleaning rest of process atributes*/
		task->pid=-1;
		task->state=FREE;
		task->quantum=0;
		task->total_trans=0;
		task->total_tics=0;
		/* preparing to make a task_switch */ 
		//It is the same as sem wait (could make a funciotn in sched??
		aux=list_first(&runqueue);
		task=list_head_to_task_struct(aux);	//Next task that will enter, modify the state
		task->state=RUN;
		task->total_trans++;
		current_quantum = task->quantum;
		task_switch((union task_union*)task);
	}
}


int sys_get_stats(int pid, struct stats *st){
	/*cheking parameters*/
	if(pid<0)  					//check if the parameter pid is valid
		return -EINVAL;
	if(st == NULL) 					//check that the task_struck is valid
		return -EFAULT;
	if(access_ok(OP_WRITE, st, STATS_SIZE)==0) 	//check that we can acces to the memory zone that we want!
		return -EFAULT;
	
	/*searching for the specified task*/
	int i;
	for(i=0;i<NR_TASKS && task[i].t.task.pid != pid;i++);		//search for the task with the pid parameter
	
	/*cheking the results of the search*/
	if (i == NR_TASKS) return -ESRCH; 		//if we can not find it, returns an error
	if (task[i].t.task.state==FREE) return -ESRCH;	//if it is not an active procces return error
	
	/* we can proced with the update of the task struck with the values of the task_struck */
	struct stats aux; 
	aux.tics = task[i].t.task.total_tics;
	aux.cs = task[i].t.task.total_trans;
	aux.remaining_quantum = current_quantum;
	copy_to_user(&aux, st, STATS_SIZE);
	return 0; //here its supossed that all goes well.
}
/*** Semafores system calls */

int sys_sem_init(int n_sem, unsigned int value)
{	if((n_sem < 0) || (n_sem >= NR_SEM))
                return (-EINVAL);
    if(sem[n_sem].owner != -1)
                return (-EBUSY);
	sem[n_sem].owner = current()->pid;
	sem[n_sem].counter = value;
	//INIT_LIST_HEAD(&sem[n_sem].sem_list);
	return 0;
}


int sys_sem_wait(int n_sem){
	if(current()->pid == 0)		return (-EPERM);
	if((n_sem < 0) || (n_sem >= NR_SEM))
                return (-EINVAL);
        if(sem[n_sem].owner == -1)
                return (-EINVAL);
	if (sem[n_sem].counter<=0) //The process will be blocked
	{
		struct task_struct* task;
		struct list_head* aux;
	//Take out from runqueue
		aux=list_first(&runqueue);
		list_del(aux);	
	//change the procces to blocked
		current()->state = BLOCKED;
		((union task_union*)current())->stack[KERNEL_STACK_SIZE-10] = 0; //returning 0 to %eax
	//addint to sem_list
		list_add_tail(aux, &(sem[n_sem].sem_list));				
	//Preparing for a task_switch
		aux=list_first(&runqueue);
		task=list_head_to_task_struct(aux);	//Next task that will enter, modify the state
		task->state=RUN;
		current_quantum = task->quantum;
		task_switch((union task_union*) task);
	}
	else sem[n_sem].counter--;
	return 0;
}

int sys_sem_signal(int n_sem){
	if((n_sem < 0) || (n_sem >= NR_SEM))
                return -EINVAL;
        if(sem[n_sem].owner == -1)
                return -EINVAL;
	if (list_empty(&sem[n_sem].sem_list)==1) sem[n_sem].counter++;
	else {
		struct task_struct* task;
		struct list_head* aux;
	//Take out from the sem_lis (semaphore queue)
		aux=list_first(&(sem[n_sem].sem_list));
		list_del(aux);	
	//change the procces to READY
		task=list_head_to_task_struct(aux);
		task->state=READY;
	//addint to runqueue
		list_add_tail(aux,&runqueue);
	}
	return 0;
}

int sys_sem_destroy(int n_sem)
{
	if((n_sem < 0) || (n_sem >= NR_SEM))
                return -EINVAL;
	if(sem[n_sem].owner == -1)
                return -EINVAL;
	if (sem[n_sem].owner != current()->pid) 
				return (-EPERM);
	//auxiliar structures
	struct task_struct* task;
	struct list_head* aux;
	union task_union *t;
	
	while (list_empty(&sem[n_sem].sem_list)!=1) //until the list will be empty
	{
		//remove one by one all the process until empty the list
		aux=list_first(&(sem[n_sem].sem_list));
		list_del(aux);	
		//change the procces to READY
		task=list_head_to_task_struct(aux);
		task->state=READY;
		t = (union task_union *) task;
		t->stack[KERNEL_STACK_SIZE-10] = -1; //send a -1 in return to this procces so it knows what happened
		//addint to runqueue
		list_add_tail(aux,&runqueue);
		
	//restor to default UNACTIVE semaphore
	}
	sem_config(n_sem);

	return 0;
}

	
int check_fd(int fd,int operation)
{
	if (fd==1)
	{
		switch (operation)
		{
			case OP_WRITE: return 0;
			case OP_READ: return (-EPERM);
			default: return (-EPERM); /*operation not permitted error*/
		}
	}
	else
	{
		return (-EBADF); /*bad file number*/
	}
}
